home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / var / lib / python-support / python2.6 / rdflib / store / BerkeleyDB.pyc (.txt) < prev    next >
Encoding:
Python Compiled Bytecode  |  2009-04-20  |  18.8 KB  |  632 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyc (Python 2.6)
  3.  
  4. import warnings
  5. warnings.warn('This Store implementation is still being debugged. It is currently running out of db lockers after adding around 2k triples.')
  6. from rdflib.store import Store, VALID_STORE, CORRUPTED_STORE, NO_STORE, UNKNOWN
  7. from rdflib.URIRef import URIRef
  8. from bsddb import db
  9. from os import mkdir, rmdir, makedirs
  10. from os.path import exists, abspath, join
  11. from urllib import pathname2url
  12. from threading import Thread
  13. from time import sleep, time
  14. import logging
  15. SUPPORT_MULTIPLE_STORE_ENVIRON = False
  16. _logger = logging.getLogger(__name__)
  17.  
  18. class BerkeleyDB(Store):
  19.     """
  20.     A transaction-capable BerkeleyDB implementation
  21.     The major difference are:
  22.       - a dbTxn attribute which is the transaction object used for all bsddb databases
  23.       - All operations (put,delete,get) take the dbTxn instance
  24.       - The actual directory used for the bsddb persistence is the name of the identifier as a subdirectory of the 'path'
  25.       
  26.     """
  27.     context_aware = True
  28.     formula_aware = True
  29.     transaction_aware = True
  30.     
  31.     def __init__(self, configuration = None, identifier = None):
  32.         self._BerkeleyDB__open = False
  33.         if not identifier or identifier:
  34.             pass
  35.         self._BerkeleyDB__identifier = 'home'
  36.         super(BerkeleyDB, self).__init__(configuration)
  37.         self.configuration = configuration
  38.         self._loads = self.node_pickler.loads
  39.         self._dumps = self.node_pickler.dumps
  40.         self._BerkeleyDB__dbTxn = None
  41.  
  42.     
  43.     def __get_identifier(self):
  44.         return self._BerkeleyDB__identifier
  45.  
  46.     identifier = property(__get_identifier)
  47.     
  48.     def destroy(self, configuration):
  49.         '''
  50.         Destroy the underlying bsddb persistence for this store
  51.         '''
  52.         if SUPPORT_MULTIPLE_STORE_ENVIRON:
  53.             fullDir = join(configuration, self.identifier)
  54.         else:
  55.             fullDir = configuration
  56.         if exists(configuration):
  57.             self.close()
  58.             db.DBEnv().remove(fullDir, db.DB_FORCE)
  59.         
  60.  
  61.     
  62.     def open(self, path, create = True):
  63.         if self._BerkeleyDB__open:
  64.             return None
  65.         homeDir = path
  66.         if SUPPORT_MULTIPLE_STORE_ENVIRON:
  67.             fullDir = join(homeDir, self.identifier)
  68.         else:
  69.             fullDir = homeDir
  70.         envsetflags = db.DB_CDB_ALLDB
  71.         envflags = db.DB_INIT_MPOOL | db.DB_INIT_LOCK | db.DB_THREAD | db.DB_INIT_TXN | db.DB_RECOVER
  72.         if not exists(fullDir):
  73.             if create == True:
  74.                 makedirs(fullDir)
  75.                 self.create(path)
  76.             else:
  77.                 return NO_STORE
  78.         create == True
  79.         if self._BerkeleyDB__identifier is None:
  80.             self._BerkeleyDB__identifier = URIRef(pathname2url(abspath(fullDir)))
  81.         
  82.         self.db_env = db_env = db.DBEnv()
  83.         db_env.set_cachesize(0, 52428800)
  84.         db_env.open(fullDir, envflags | db.DB_CREATE, 0)
  85.         self.dbTxn = db_env.txn_begin()
  86.         self._BerkeleyDB__open = True
  87.         dbname = None
  88.         dbtype = db.DB_BTREE
  89.         dbopenflags = db.DB_THREAD
  90.         dbmode = 432
  91.         dbsetflags = 0
  92.         self._BerkeleyDB__indicies = [
  93.             None] * 3
  94.         self._BerkeleyDB__indicies_info = [
  95.             None] * 3
  96.         for i in xrange(0, 3):
  97.             index_name = to_key_func(i)(('s', 'p', 'o'), 'c')
  98.             index = db.DB(db_env)
  99.             index.set_flags(dbsetflags)
  100.             index.open(index_name, dbname, dbtype, dbopenflags | db.DB_CREATE, dbmode, txn = self.dbTxn)
  101.             self._BerkeleyDB__indicies[i] = index
  102.             self._BerkeleyDB__indicies_info[i] = (index, to_key_func(i), from_key_func(i))
  103.         
  104.         lookup = { }
  105.         for i in xrange(0, 8):
  106.             results = []
  107.             for start in xrange(0, 3):
  108.                 score = 1
  109.                 len = 0
  110.                 for j in xrange(start, start + 3):
  111.                     if i & 1 << j % 3:
  112.                         score = score << 1
  113.                         len += 1
  114.                         continue
  115.                 
  116.                 tie_break = 2 - start
  117.                 results.append(((score, tie_break), start, len))
  118.             
  119.             results.sort()
  120.             (score, start, len) = results[-1]
  121.             
  122.             def get_prefix_func(start, end):
  123.                 
  124.                 def get_prefix(triple, context):
  125.                     if context is None:
  126.                         yield ''
  127.                     else:
  128.                         yield context
  129.                     i = start
  130.                     while i < end:
  131.                         yield triple[i % 3]
  132.                         i += 1
  133.                     yield ''
  134.  
  135.                 return get_prefix
  136.  
  137.             lookup[i] = (self._BerkeleyDB__indicies[start], get_prefix_func(start, start + len), from_key_func(start), results_from_key_func(start, self._from_string))
  138.         
  139.         self._BerkeleyDB__lookup_dict = lookup
  140.         self._BerkeleyDB__contexts = db.DB(db_env)
  141.         self._BerkeleyDB__contexts.set_flags(dbsetflags)
  142.         self._BerkeleyDB__contexts.open('contexts', dbname, dbtype, dbopenflags | db.DB_CREATE, dbmode, txn = self.dbTxn)
  143.         self._BerkeleyDB__namespace = db.DB(db_env)
  144.         self._BerkeleyDB__namespace.set_flags(dbsetflags)
  145.         self._BerkeleyDB__namespace.open('namespace', dbname, dbtype, dbopenflags | db.DB_CREATE, dbmode, txn = self.dbTxn)
  146.         self._BerkeleyDB__prefix = db.DB(db_env)
  147.         self._BerkeleyDB__prefix.set_flags(dbsetflags)
  148.         self._BerkeleyDB__prefix.open('prefix', dbname, dbtype, dbopenflags | db.DB_CREATE, dbmode, txn = self.dbTxn)
  149.         self._BerkeleyDB__i2k = db.DB(db_env)
  150.         self._BerkeleyDB__i2k.set_flags(dbsetflags)
  151.         self._BerkeleyDB__i2k.open('i2k', dbname, db.DB_HASH, dbopenflags | db.DB_CREATE, dbmode, txn = self.dbTxn)
  152.         self._BerkeleyDB__needs_sync = False
  153.         t = Thread(target = self._BerkeleyDB__sync_run)
  154.         t.setDaemon(True)
  155.         t.start()
  156.         self._BerkeleyDB__sync_thread = t
  157.         return VALID_STORE
  158.  
  159.     
  160.     def __sync_run(self):
  161.         (min_seconds, max_seconds) = (10, 300)
  162.         while self._BerkeleyDB__open:
  163.             if self._BerkeleyDB__needs_sync:
  164.                 t0 = t1 = time()
  165.                 self._BerkeleyDB__needs_sync = False
  166.                 while self._BerkeleyDB__open:
  167.                     sleep(0.1)
  168.                     if self._BerkeleyDB__needs_sync:
  169.                         t1 = time()
  170.                         self._BerkeleyDB__needs_sync = False
  171.                     
  172.                     if time() - t1 > min_seconds or time() - t0 > max_seconds:
  173.                         self._BerkeleyDB__needs_sync = False
  174.                         _logger.debug('sync')
  175.                         self.sync()
  176.                         break
  177.                         continue
  178.                 continue
  179.             sleep(1)
  180.  
  181.     
  182.     def sync(self):
  183.         if self._BerkeleyDB__open:
  184.             for i in self._BerkeleyDB__indicies:
  185.                 i.sync()
  186.             
  187.             self._BerkeleyDB__contexts.sync()
  188.             self._BerkeleyDB__namespace.sync()
  189.             self._BerkeleyDB__prefix.sync()
  190.             self._BerkeleyDB__i2k.sync()
  191.         
  192.  
  193.     
  194.     def commit(self):
  195.         '''
  196.         Bsddb tx objects cannot be reused after commit 
  197.         '''
  198.         if self.dbTxn:
  199.             _logger.debug('commiting')
  200.             self.dbTxn.commit(0)
  201.             self.dbTxn = self.db_env.txn_begin()
  202.         else:
  203.             _logger.warning('No transaction to commit')
  204.  
  205.     
  206.     def rollback(self):
  207.         '''
  208.         Bsddb tx objects cannot be reused after commit
  209.         '''
  210.         if self.dbTxn is not None:
  211.             _logger.debug('rollingback')
  212.             self.dbTxn.abort()
  213.             self.dbTxn = None
  214.         else:
  215.             _logger.warning('No transaction to rollback')
  216.  
  217.     
  218.     def __del__(self):
  219.         """
  220.         Redirects python's native garbage collection into Store.close 
  221.         """
  222.         self.close()
  223.  
  224.     
  225.     def close(self, commit_pending_transaction = False):
  226.         '''
  227.         Properly handles transactions explicitely (with parameter) or by default
  228.         '''
  229.         if not self._BerkeleyDB__open:
  230.             return None
  231.         if self.dbTxn:
  232.             if not commit_pending_transaction:
  233.                 self.rollback()
  234.             else:
  235.                 self.commit()
  236.                 self.dbTxn.abort()
  237.         
  238.         self._BerkeleyDB__open = False
  239.         self._BerkeleyDB__sync_thread.join()
  240.         for i in self._BerkeleyDB__indicies:
  241.             i.close()
  242.         
  243.         self._BerkeleyDB__contexts.close()
  244.         self._BerkeleyDB__namespace.close()
  245.         self._BerkeleyDB__prefix.close()
  246.         self._BerkeleyDB__i2k.close()
  247.         self.db_env.close()
  248.  
  249.     
  250.     def add(self, .1, context, quoted = False):
  251.         '''        Add a triple to the store of triples.
  252.         '''
  253.         (subject, predicate, object_) = .1
  254.         if not self._BerkeleyDB__open:
  255.             raise AssertionError, 'The Store must be open.'
  256.         if not context != self:
  257.             raise AssertionError, 'Can not add triple directly to store'
  258.         Store.add(self, (subject, predicate, object_), context, quoted)
  259.         _to_string = self._to_string
  260.         s = _to_string(subject)
  261.         p = _to_string(predicate)
  262.         o = _to_string(object_)
  263.         c = _to_string(context)
  264.         (cspo, cpos, cosp) = self._BerkeleyDB__indicies
  265.         value = cspo.get('%s^%s^%s^%s^' % (c, s, p, o), txn = self.dbTxn)
  266.         if value is None:
  267.             self._BerkeleyDB__contexts.put(c, '', self.dbTxn)
  268.             if not cspo.get('%s^%s^%s^%s^' % ('', s, p, o), txn = self.dbTxn):
  269.                 pass
  270.             contexts_value = ''
  271.             contexts = set(contexts_value.split('^'))
  272.             contexts.add(c)
  273.             contexts_value = '^'.join(contexts)
  274.             if not contexts_value != None:
  275.                 raise AssertionError
  276.             cspo.put('%s^%s^%s^%s^' % (c, s, p, o), '', self.dbTxn)
  277.             cpos.put('%s^%s^%s^%s^' % (c, p, o, s), '', self.dbTxn)
  278.             cosp.put('%s^%s^%s^%s^' % (c, o, s, p), '', self.dbTxn)
  279.             self._BerkeleyDB__needs_sync = True
  280.         
  281.  
  282.     
  283.     def __remove(self, .1, c, quoted = False):
  284.         (s, p, o) = .1
  285.         (cspo, cpos, cosp) = self._BerkeleyDB__indicies
  286.         if not cspo.get('^'.join(('', s, p, o, '')), txn = self.dbTxn):
  287.             pass
  288.         contexts_value = ''
  289.         contexts = set(contexts_value.split('^'))
  290.         contexts.discard(c)
  291.         contexts_value = '^'.join(contexts)
  292.         for i, _to_key, _from_key in self._BerkeleyDB__indicies_info:
  293.             i.delete(_to_key((s, p, o), c), txn = self.dbTxn)
  294.         
  295.         if not quoted:
  296.             if contexts_value:
  297.                 for i, _to_key, _from_key in self._BerkeleyDB__indicies_info:
  298.                     i.put(_to_key((s, p, o), ''), contexts_value, self.dbTxn)
  299.                 
  300.             else:
  301.                 for i, _to_key, _from_key in self._BerkeleyDB__indicies_info:
  302.                     
  303.                     try:
  304.                         i.delete(_to_key((s, p, o), ''), txn = self.dbTxn)
  305.                     continue
  306.                     except db.DBNotFoundError:
  307.                         e = None
  308.                         continue
  309.                     
  310.  
  311.                 
  312.         
  313.  
  314.     
  315.     def remove(self, .1, context):
  316.         (subject, predicate, object_) = .1
  317.         if not self._BerkeleyDB__open:
  318.             raise AssertionError, 'The Store must be open.'
  319.         Store.remove(self, (subject, predicate, object_), context)
  320.         _to_string = self._to_string
  321.         if context is not None:
  322.             if context == self:
  323.                 context = None
  324.             
  325.         
  326.         if subject is not None and predicate is not None and object_ is not None and context is not None:
  327.             s = _to_string(subject)
  328.             p = _to_string(predicate)
  329.             o = _to_string(object_)
  330.             c = _to_string(context)
  331.             value = self._BerkeleyDB__indicies[0].get('%s^%s^%s^%s^' % (c, s, p, o), txn = self.dbTxn)
  332.             if value is not None:
  333.                 self._BerkeleyDB__remove((s, p, o), c)
  334.                 self._BerkeleyDB__needs_sync = True
  335.             
  336.         else:
  337.             (cspo, cpos, cosp) = self._BerkeleyDB__indicies
  338.             (index, prefix, from_key, results_from_key) = self._BerkeleyDB__lookup((subject, predicate, object_), context)
  339.             cursor = index.cursor(txn = self.dbTxn)
  340.             
  341.             try:
  342.                 current = cursor.set_range(prefix)
  343.                 needs_sync = True
  344.             except db.DBNotFoundError:
  345.                 current = None
  346.                 needs_sync = False
  347.  
  348.             cursor.close()
  349.             while current:
  350.                 (key, value) = current
  351.                 cursor = index.cursor(txn = self.dbTxn)
  352.                 
  353.                 try:
  354.                     cursor.set_range(key)
  355.                     current = cursor.next()
  356.                 except db.DBNotFoundError:
  357.                     current = None
  358.  
  359.                 cursor.close()
  360.                 if key.startswith(prefix):
  361.                     (c, s, p, o) = from_key(key)
  362.                     if context is None:
  363.                         if not index.get(key, txn = self.dbTxn):
  364.                             pass
  365.                         contexts_value = ''
  366.                         contexts = set(contexts_value.split('^'))
  367.                         contexts.add('')
  368.                         for c in contexts:
  369.                             for i, _to_key, _ in self._BerkeleyDB__indicies_info:
  370.                                 i.delete(_to_key((s, p, o), c), txn = self.dbTxn)
  371.                             
  372.                         
  373.                     else:
  374.                         self._BerkeleyDB__remove((s, p, o), c)
  375.                 context is None
  376.                 break
  377.             if context is not None:
  378.                 if subject is None and predicate is None and object_ is None:
  379.                     
  380.                     try:
  381.                         self._BerkeleyDB__contexts.delete(_to_string(context), txn = self.dbTxn)
  382.                     except db.DBNotFoundError:
  383.                         e = None
  384.                     except:
  385.                         None<EXCEPTION MATCH>db.DBNotFoundError
  386.                     
  387.  
  388.                 None<EXCEPTION MATCH>db.DBNotFoundError
  389.             
  390.             self._BerkeleyDB__needs_sync = needs_sync
  391.  
  392.     
  393.     def triples(self, .1, context = None):
  394.         '''A generator over all the triples matching '''
  395.         (subject, predicate, object_) = .1
  396.         if not self._BerkeleyDB__open:
  397.             raise AssertionError, 'The Store must be open.'
  398.         if context is not None:
  399.             if context == self:
  400.                 context = None
  401.             
  402.         
  403.         _from_string = self._from_string
  404.         (index, prefix, from_key, results_from_key) = self._BerkeleyDB__lookup((subject, predicate, object_), context)
  405.         cursor = index.cursor(txn = self.dbTxn)
  406.         
  407.         try:
  408.             current = cursor.set_range(prefix)
  409.         except db.DBNotFoundError:
  410.             current = None
  411.  
  412.         cursor.close()
  413.         while current:
  414.             (key, value) = current
  415.             cursor = index.cursor(txn = self.dbTxn)
  416.             
  417.             try:
  418.                 cursor.set_range(key)
  419.                 current = cursor.next()
  420.             except db.DBNotFoundError:
  421.                 current = None
  422.  
  423.             cursor.close()
  424.             if key and key.startswith(prefix):
  425.                 contexts_value = index.get(key, txn = self.dbTxn)
  426.                 yield results_from_key(key, subject, predicate, object_, contexts_value)
  427.                 continue
  428.             break
  429.  
  430.     
  431.     def __len__(self, context = None):
  432.         if not self._BerkeleyDB__open:
  433.             raise AssertionError, 'The Store must be open.'
  434.         if context is not None:
  435.             if context == self:
  436.                 context = None
  437.             
  438.         
  439.         if context is None:
  440.             prefix = '^'
  441.         else:
  442.             prefix = '%s^' % self._to_string(context)
  443.         index = self._BerkeleyDB__indicies[0]
  444.         cursor = index.cursor(txn = self.dbTxn)
  445.         current = cursor.set_range(prefix)
  446.         count = 0
  447.         while current:
  448.             (key, value) = current
  449.             if key.startswith(prefix):
  450.                 count += 1
  451.                 current = cursor.next()
  452.                 continue
  453.             break
  454.         cursor.close()
  455.         return count
  456.  
  457.     
  458.     def bind(self, prefix, namespace):
  459.         prefix = prefix.encode('utf-8')
  460.         namespace = namespace.encode('utf-8')
  461.         bound_prefix = self._BerkeleyDB__prefix.get(namespace, txn = self.dbTxn)
  462.         if bound_prefix:
  463.             self._BerkeleyDB__namespace.delete(bound_prefix, txn = self.dbTxn)
  464.         
  465.         self._BerkeleyDB__prefix.put(namespace, prefix, self.dbTxn)
  466.         self._BerkeleyDB__namespace.put(prefix, namespace, self.dbTxn)
  467.  
  468.     
  469.     def namespace(self, prefix):
  470.         prefix = prefix.encode('utf-8')
  471.         return self._BerkeleyDB__namespace.get(prefix, None, txn = self.dbTxn)
  472.  
  473.     
  474.     def prefix(self, namespace):
  475.         namespace = namespace.encode('utf-8')
  476.         return self._BerkeleyDB__prefix.get(namespace, None, txn = self.dbTxn)
  477.  
  478.     
  479.     def namespaces(self):
  480.         cursor = self._BerkeleyDB__namespace.cursor(txn = self.dbTxn)
  481.         results = []
  482.         current = cursor.first()
  483.         while current:
  484.             (prefix, namespace) = current
  485.             results.append((prefix, namespace))
  486.             current = cursor.next()
  487.         cursor.close()
  488.         for prefix, namespace in results:
  489.             yield (prefix, URIRef(namespace))
  490.         
  491.  
  492.     
  493.     def contexts(self, triple = None):
  494.         _from_string = self._from_string
  495.         _to_string = self._to_string
  496.         if triple:
  497.             (s, p, o) = triple
  498.             s = _to_string(s)
  499.             p = _to_string(p)
  500.             o = _to_string(o)
  501.             contexts = self._BerkeleyDB__indicies[0].get('%s^%s^%s^%s^' % ('', s, p, o), txn = self.dbTxn)
  502.             if contexts:
  503.                 for c in contexts.split('^'):
  504.                     if c:
  505.                         yield _from_string(c)
  506.                         continue
  507.                 
  508.             
  509.         else:
  510.             index = self._BerkeleyDB__contexts
  511.             cursor = index.cursor(txn = self.dbTxn)
  512.             current = cursor.first()
  513.             cursor.close()
  514.             while current:
  515.                 (key, value) = current
  516.                 context = _from_string(key)
  517.                 yield context
  518.                 cursor = index.cursor(txn = self.dbTxn)
  519.                 
  520.                 try:
  521.                     cursor.set_range(key)
  522.                     current = cursor.next()
  523.                 except db.DBNotFoundError:
  524.                     current = None
  525.  
  526.                 cursor.close()
  527.  
  528.     
  529.     def _from_string(self, i):
  530.         k = self._BerkeleyDB__i2k.get(i, txn = self.dbTxn)
  531.         return self._loads(k)
  532.  
  533.     
  534.     def _to_string(self, term):
  535.         '''
  536.         i2k:  hashString -> pickledTerm
  537.         
  538.         i2k basically stores the reverse lookup of the MD5 hash of the term 
  539.         
  540.         '''
  541.         if not term is not None:
  542.             raise AssertionError
  543.         i = term.md5_term_hash()
  544.         k = self._BerkeleyDB__i2k.get(i, txn = self.dbTxn)
  545.         if k is None:
  546.             self._BerkeleyDB__i2k.put(i, self._dumps(term), txn = self.dbTxn)
  547.         
  548.         return i
  549.  
  550.     
  551.     def __lookup(self, .1, context):
  552.         (subject, predicate, object_) = .1
  553.         _to_string = self._to_string
  554.         if context is not None:
  555.             context = _to_string(context)
  556.         
  557.         i = 0
  558.         if subject is not None:
  559.             i += 1
  560.             subject = _to_string(subject)
  561.         
  562.         if predicate is not None:
  563.             i += 2
  564.             predicate = _to_string(predicate)
  565.         
  566.         if object_ is not None:
  567.             i += 4
  568.             object_ = _to_string(object_)
  569.         
  570.         (index, prefix_func, from_key, results_from_key) = self._BerkeleyDB__lookup_dict[i]
  571.         prefix = '^'.join(prefix_func((subject, predicate, object_), context))
  572.         return (index, prefix, from_key, results_from_key)
  573.  
  574.  
  575.  
  576. def to_key_func(i):
  577.     
  578.     def to_key(triple, context):
  579.         '''Takes a string; returns key'''
  580.         return '^'.join((context, triple[i % 3], triple[(i + 1) % 3], triple[(i + 2) % 3], ''))
  581.  
  582.     return to_key
  583.  
  584.  
  585. def from_key_func(i):
  586.     
  587.     def from_key(key):
  588.         '''Takes a key; returns string'''
  589.         parts = key.split('^')
  590.         return (parts[0], parts[((3 - i) + 0) % 3 + 1], parts[((3 - i) + 1) % 3 + 1], parts[((3 - i) + 2) % 3 + 1])
  591.  
  592.     return from_key
  593.  
  594.  
  595. def results_from_key_func(i, from_string):
  596.     
  597.     def from_key(key, subject, predicate, object_, contexts_value):
  598.         '''Takes a key and subject, predicate, object; returns tuple for yield'''
  599.         parts = key.split('^')
  600.         if subject is None:
  601.             s = from_string(parts[((3 - i) + 0) % 3 + 1])
  602.         else:
  603.             s = subject
  604.         if predicate is None:
  605.             p = from_string(parts[((3 - i) + 1) % 3 + 1])
  606.         else:
  607.             p = predicate
  608.         if object_ is None:
  609.             o = from_string(parts[((3 - i) + 2) % 3 + 1])
  610.         else:
  611.             o = object_
  612.         return (((s, p, o),), (lambda .0: for c in .0:
  613. if c:
  614. from_string(c)continue)(contexts_value.split('^')))
  615.  
  616.     return from_key
  617.  
  618.  
  619. def readable_index(i):
  620.     (s, p, o) = '???'
  621.     if i & 1:
  622.         s = 's'
  623.     
  624.     if i & 2:
  625.         p = 'p'
  626.     
  627.     if i & 4:
  628.         o = 'o'
  629.     
  630.     return '%s,%s,%s' % (s, p, o)
  631.  
  632.